using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Library.IniFiles
{
    public class IniFileReader
    {
        private static bool _allowInlineComments = true;
        private static bool _allowMultiValuePairs = true;
        private static bool _allowLeadingSpaces = false;

        public event EventHandler<IniReaderEventArgs> ItemRead;

        public IniFileReader() {}

        public IniFileReader(EventHandler<IniReaderEventArgs> itemReadHandler) 
        {
            this.ItemRead += itemReadHandler;
        }
 
        private void OnItemRead(IniReaderEventArgs args)
        {
            if (ItemRead != null)
            {
                ItemRead(this, args);
            }
        }

        public void Read(string fileName)
        {
            FileInfo finf = new FileInfo(fileName);
            if (finf.Exists)
            {
                Read(finf.OpenText());
            }
        }

        private enum ValueReadMode
        {
            Key = 0,
            Assignment = 1,
            QuotedValue = 2
        }

        public void Read(StreamReader reader)
        {
            string currentSection = IniSection.HEADER_SECTION_TITLE;
            string lineComment = string.Empty;
            string key;
            string val;
            List<string> vals;
            
            try 
            {
                string line = reader.ReadLine();
                while (line != null) 
                {
                    if (_allowLeadingSpaces) line = line.TrimStart();
                    
                    if ((line.Length == 0) ||
                        (Char.IsWhiteSpace(line[0]))) // skip this line, it's empty or has whitespace!
                    {
                        // Skip this line!
                    }
                    else if (line.StartsWith("[")) // a new section has been created, so stop the old, and start the new
                    {
                        currentSection = string.Empty;
                        lineComment = string.Empty;
                        bool start = true;

                        for (int i=1; i < line.Length; i++)
                        {
                            char c = line[i];
                            if (start)
                            {
                                if (c == ']')
                                {
                                    start = false;
                                }
                                else 
                                {
                                    currentSection += c;
                                }
                            }
                            else
                            {
                                if (c == ';' && _allowInlineComments)
                                {
                                    lineComment = line.Substring(i+1);
                                }
                            }
                        }

                        IniReaderEventArgs args = new IniReaderEventArgs();
                        args.SectionTitle = currentSection;
                        args.IsSectionHeader = true;
                        args.LineComment = lineComment;
                        this.OnItemRead(args);
                    }
                    else if (line.StartsWith(";")) // a full line comment is here
                    {
                        lineComment = line.Substring(1);

                        IniReaderEventArgs args = new IniReaderEventArgs();
                        args.SectionTitle = currentSection;
                        args.IsFullLineComment = true;
                        args.LineComment = lineComment;
                        this.OnItemRead(args);
                    }
                    else // this is a line with a key value pair
                    {
                        lineComment = string.Empty;
                        key = string.Empty;
                        val = string.Empty;
                        vals = new List<string>();
                        ValueReadMode valueReadMode = ValueReadMode.Key;
                        bool waitForQuote = true;
                        bool lastWasQuote = true;

                        do
                        {
                            char c = '\0';
                            for (int i = 0; i < line.Length; i++)
                            {
                                c = line[i];
                                if (valueReadMode == ValueReadMode.Key) 
                                {
                                    if (c == '=')
                                    {
                                        valueReadMode = ValueReadMode.Assignment;
                                        waitForQuote = true;
                                    }
                                    else
                                    {
                                        key += c;
                                    }
                                }
                                else if (valueReadMode == ValueReadMode.Assignment) 
                                {
                                    if (waitForQuote)
                                    {
                                        if (c == '\"')
                                        {
                                            lastWasQuote = false;
                                            valueReadMode = ValueReadMode.QuotedValue;
                                            waitForQuote = false;
                                        }
                                        else if (c == ';' && _allowInlineComments)
                                        {
                                            lineComment = line.Substring(i + 1);
                                            waitForQuote = false;
                                            break;
                                        }
                                        else if (Char.IsWhiteSpace(c) || (c == ',' && _allowMultiValuePairs))
                                        {
                                            waitForQuote = true;
                                        }
                                        else
                                        {
                                            val += c;
                                            waitForQuote = false;
                                        }
                                    }
                                    else
                                    {
                                        if (c == ',' && _allowMultiValuePairs)
                                        {
                                            vals.Add(val.Trim());
                                            val = string.Empty;

                                            waitForQuote = true;
                                        }
                                        else if (c == ';' && _allowInlineComments)
                                        {
                                            vals.Add(val.Trim());
                                            val = string.Empty;

                                            lineComment = line.Substring(i + 1);
                                            break;
                                        }
                                        else
                                        {
                                            val += c;
                                        }
                                    }
                                }
                                else if (valueReadMode == ValueReadMode.QuotedValue)
                                {
                                    if (c == '\"')
                                    {
                                        if (lastWasQuote)
                                        {
                                            lastWasQuote = false;
                                            val += c;
                                        }
                                        else
                                        {
                                            lastWasQuote = true;
                                        }
                                    }
                                    else
                                    {
                                        if (lastWasQuote)
                                        {
                                            lastWasQuote = false;
                                            vals.Add(val.Trim());
                                            val = string.Empty;
                                            valueReadMode = ValueReadMode.Assignment;

                                            if ((c == ',') && _allowMultiValuePairs)
                                                waitForQuote = true;

                                            if ((c == ';') && _allowInlineComments)
                                            {
                                                lineComment = line.Substring(i + 1);
                                                break;
                                            }
                                            else if (!_allowMultiValuePairs)
                                            {
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            val += c;
                                        }
                                    }
                                }
                            }

                            if ((waitForQuote && (c == '=') && (valueReadMode == ValueReadMode.Assignment)) ||
                                (!waitForQuote && (valueReadMode == ValueReadMode.Assignment)) ||
                                (lastWasQuote && (valueReadMode == ValueReadMode.QuotedValue)))
                            {
                                valueReadMode = ValueReadMode.Key;
                                if (val.Length > 0)
                                {
                                    vals.Add(val.Trim());
                                    val = string.Empty;
                                }

                                IniReaderEventArgs args = new IniReaderEventArgs();
                                args.SectionTitle = currentSection;
                                args.Key = key;
                                args.Values = vals;
                                args.LineComment = lineComment;
                                this.OnItemRead(args);
                            }
                            else
                            {
                                line = reader.ReadLine();
                                if (line == null) // if we're at the last line, save what we have and jump out!
                                {
                                    valueReadMode = ValueReadMode.Key;
                                    vals.Add(val.Trim());
                                    val = string.Empty;

                                    IniReaderEventArgs args = new IniReaderEventArgs();
                                    args.SectionTitle = currentSection;
                                    args.Key = key;
                                    args.Values = vals;
                                    args.LineComment = lineComment;
                                    this.OnItemRead(args);
                                    break;
                                }
                                else
                                {
                                    val += Environment.NewLine;
                                }
                            }
                        } while (valueReadMode == ValueReadMode.QuotedValue);
                    }

                    if (line != null)
                    {
                        line = reader.ReadLine();
                    }
                }
            }
            catch (Exception ex) 
            {
                System.Console.Out.WriteLine(ex.Message);
                System.Console.Out.WriteLine(ex.StackTrace);
            }
            finally
            {
                reader.Close();
                reader = null;
            }
        }

        public static bool AllowInlineComments
        {
            get
            {
                return _allowInlineComments;
            }
            set
            {
                _allowInlineComments = value;
            }
        }

        public static bool AllowMultiValuePairs
        {
            get
            {
                return _allowMultiValuePairs;
            }
            set
            {
                _allowMultiValuePairs = value;
            }
        }

        public static bool AllowLeadingSpaces
        {
            get
            {
                return _allowLeadingSpaces;
            }
            set
            {
                _allowLeadingSpaces = value;
            }
        }
    }

    public class IniReaderEventArgs : EventArgs
    {
        private string section;
        private string key = string.Empty;
        private List<string> values = null;
        private string lineComment = string.Empty;
        private bool isComment = false;
        private bool isSectionHeader = false;

        public IniReaderEventArgs() {}

        public string SectionTitle
        {
            get { return section; }
            set { section = value; }
        }

        public string Key
        {
            get { return key; }
            set { if (value != null) key = value.Trim(); }
        }

        public List<string> Values
        {
            get { return values; }
            set { values = value; }
        }

        public string LineComment
        {
            get { return lineComment; }
            set { lineComment = value; }
        }

        public bool IsFullLineComment
        {
            get { return isComment; }
            set { isComment = value; }
        }

        public bool IsSectionHeader
        {
            get { return isSectionHeader; }
            set { isSectionHeader = value; }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            if (IsFullLineComment)
            {
                sb.Append("FULL LINE COMMENT: ;;").Append(LineComment);
            }
            else if (IsSectionHeader)
            {
                sb.Append("SECTION HEADER: [[").Append(SectionTitle).Append("]]");
                sb.Append(" ;;").Append(LineComment).AppendLine();
            }
            else
            {
                sb.Append("SECTION DATA: [[").Append(SectionTitle).Append("]] [[").Append(Key).Append("]]");
                foreach (string val in values) sb.Append(" [[").Append(val).Append("]]");
                sb.Append(" ;;").Append(LineComment).AppendLine();
            }
            return sb.ToString();
        }
    }
}
